package core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;

import exception.ColumnExistsException;
import exception.ConstraintException;
import exception.DataException;
import exception.NoSuchColumnFoundException;
import exception.NoSuchDatabaseFoundException;
import exception.NoSuchTableFoundException;

public class Table {
	private String tableName;
	private LinkedHashMap<String, Column> columns;
	private LinkedList<Record> records;
	private LinkedHashMap<String, Constraint> constraints;
	private Constraint primaryKeyConstraint;
	private LinkedHashMap<String, Constraint> foreignKeyConstraints;
	
	public Table(String tableName) {
		this.tableName = tableName;
		columns = new LinkedHashMap<String, Column>();
		records = new LinkedList<Record>();
		constraints = new LinkedHashMap<String, Constraint>();
		primaryKeyConstraint = null;
		foreignKeyConstraints = new LinkedHashMap<String, Constraint>();
	}
	
	public String getTableName() {
		return tableName;
	}
	
	public Constraint getPrimaryKeyConstraint() {
		return primaryKeyConstraint;
	}
	
	public LinkedHashMap<String, Constraint> getForeignKeyConstraints() {
		return foreignKeyConstraints;
	}
	
	public LinkedList<Record> getRecords() {
		return records;
	}

	public void addColumn(String columnName, int position, String defaultValue, String dataType, int characterLength, boolean isNull) throws ColumnExistsException{
		if(columns.containsKey(columnName)) {
			throw new ColumnExistsException("Column '"+ columnName +"' already exists");
		}
		columns.put(columnName, new Column(columnName, position, defaultValue, dataType, characterLength, isNull, this));
	}
	
	public Column getColumn(String columnName) throws NoSuchColumnFoundException {
		if(!columns.containsKey(columnName)) {
			throw new NoSuchColumnFoundException("Column "+ columnName + " does not exists");
		}
		
		return columns.get(columnName);
	}
	
	public HashMap<String, Column> getColumns() {
		return columns;
	}
	
	public void addConstraints(RDBMS rdbms, ArrayList<String> constraints) throws NoSuchColumnFoundException, NoSuchDatabaseFoundException, NoSuchTableFoundException, ConstraintException {
		for(String constraint : constraints) {
			addConstraint(rdbms, constraint);
		}
	}

	public void addConstraint(RDBMS rdbms, String constraint) throws NoSuchColumnFoundException, NoSuchDatabaseFoundException, NoSuchTableFoundException, ConstraintException {
		String[] attributes = constraint.split(":");
		
		Constraint con;
		if(constraints.containsKey(attributes[0])) {
			con = constraints.get(attributes[0]);
		} else {
			con = new Constraint(attributes[0], attributes[1]);
			constraints.put(attributes[0], con);
			
			if(attributes[1].equals("PRIMARY KEY")) {
				primaryKeyConstraint = con;
			} else {
				foreignKeyConstraints.put(attributes[0], con);
			}
		}
		
		for(int i = 2; i < attributes.length; i++) {
			
			String[] conAtt = attributes[i].split("\\|");

			Column col = null;
			Column refCol = null;
			
			if(!columns.containsKey(conAtt[0])) {
				throw new NoSuchColumnFoundException("Column "+ conAtt[0] +" does not exists");
			}
			
			col = columns.get(conAtt[0]);
			
			// Als constraints attributes array uit 4 delen bevat en geen NULL bevat beschouwen we
			// het als een constraint reference
			if(conAtt.length == 4 && !conAtt[1].equals("NULL") && !conAtt[2].equals("NULL") && !conAtt[3].equals("NULL")) {
				refCol = rdbms.getColumn(conAtt[1], conAtt[2], conAtt[3]);
				
				if(refCol.getTable().getPrimaryKeyConstraint() == null) {
					throw new ConstraintException("Error");
				}
				if(!refCol.getTable().getPrimaryKeyConstraint().hasColumn(refCol)){
					throw new ConstraintException("referenced column is not in Primary key");
				}
				
			}

			if(col.equals(refCol)) {
				throw new ConstraintException("Reference column "+ col.getColumnName() +" is referencing to itself");
			}
			
			con.addContraintReference(new ConstraintReference(col, refCol));
		}
		
		if(con.getType().equals("FOREIGN KEY")){
			ArrayList<ConstraintReference> refs = con.getContraintReference();
			ArrayList<ConstraintReference> PKrefs = refs.get(0).getRefColumn().getTable().primaryKeyConstraint.getContraintReference();
			if(refs.size()!=PKrefs.size()){
				constraints.remove(attributes[0]);
				foreignKeyConstraints.remove(attributes[0]);
				throw new ConstraintException("too few columns in FOREIGN KEY");
			}
		}
	}
	
	public HashMap<String,Constraint> getConstraints() {
		return constraints;
	}

	public void dropColumn(String columnName) throws NoSuchColumnFoundException, ConstraintException {
		if(!columns.containsKey(columnName)){
			throw new NoSuchColumnFoundException("Column "+columnName+" does not exists");
		}
		
		if(!isEditebleColumn(columns.get(columnName))){
			throw new ConstraintException("Column "+columnName+" can not be edited");
		}

		columns.remove(columnName);
	}
	
	public void alterColumn(String columnName, String type) throws NoSuchColumnFoundException, ConstraintException {
		if(!columns.containsKey(columnName)){
			throw new NoSuchColumnFoundException("Column "+columnName+" does not exists");
		}
		
		if(!isEditebleColumn(columns.get(columnName))){
			throw new ConstraintException("Column "+columnName+" can not be edited");
		}
		columns.get(columnName).setType(type);
	}
	
	public void insert(HashMap<String, String> values) throws NoSuchColumnFoundException, DataException, ConstraintException {
		Record record = new Record();
		
		if(values.size() > columns.size()) {
			throw new DataException("Too many values, got "+ values.size() +" but max "+ columns.size());
		}
		
		record.insert(columns, values);
		
		records.add(record);
	}
	
	public HashMap<String, String> select(String columns, String[] conditions) throws NoSuchColumnFoundException {
		HashMap<String, String> output = new HashMap<String, String>();
		String[] columnsArray = columns.split(":");
		
		for(int i = 0; i < columnsArray.length; i++) {
			if(!this.columns.containsKey(columnsArray[i])) {
				throw new NoSuchColumnFoundException("Column "+ columnsArray[i] +" does not exists");
			}
			
			Column column = this.columns.get(columnsArray[i]);
			
			for(Record record : records) {
				output.put(columnsArray[i], record.getData().get(column));
			}
		}
		
		return output;		
	}

	public void delete(String[] conditions) throws NoSuchColumnFoundException {
		
	}
	
	public void update(String[] update, String[] conditions) throws NoSuchColumnFoundException {
		
	}
	
	private boolean isEditebleColumn(Column col){
		for(Constraint con:constraints.values()){
			if(con.hasColumn(col)){
				return false;
			}
		}
		return true;
	}

	public boolean isReferenced(Table table) {
		for(Constraint con:constraints.values()){
			if(con.isReferenced(table)){
				return true;
			}
		}
		return false;
	}
}
